
// 检测全局命名空间是否存在
const __Global__ = window ?? globalThis;
if(!Reflect.has(__Global__, 'mtec')){
	Reflect.set(__Global__, 'mtec', {});
	Reflect.set(mtec, '__mtec__', 'Many technologies');
}

mtec.getype = function(value){
	let type = typeof value;
	return type=='object' ? value==null ? 'null' : Array.isArray(value) ? 'array' : 'object' : type;
}

mtec.same = function(a, b, record){
	if(a===b) return true;

	let [ta, tb] = [a, b].map(_=>mtec.getype(_));

	if(ta!=tb) return [ta, tb].every(t=>['number', 'bigint'].includes(t)) ? a==b : false;

	if(!['array', 'object'].includes(ta)) return a==b;

	if(!record) record = {cache: [], mark: new Map(), list: []};

	let token = [a, b].map(v=>{
		if(record.mark.has(v)) return record.mark.get(v);

		let mark = mtec.string.randomToken(3, 36, _t=>!record.list.includes(_t));
		record.list.push(mark);
		record.mark.set(v, mark);
		return mark;
	}).sort().join('::');

	if(record.cache.includes(token)) return true;
	record.cache.push(token);

	let [key_a, key_b] = [a, b].map(v=>Reflect.ownKeys(v).filter(k=>v[k]!=undefined && v[k]!=null).sort());

	if(key_a.length==key_b.length && key_a.every((k, i)=>k==key_b[i])) return key_a.every(k=>mtec.same(a[k], b[k], record));

	return false;
}

mtec.pickValueByType = function<T extends [mtec.type_string, any][]>(values: any[], types: T){
	let map = values.reduce((m, v)=>{
		let t = mtec.getype(v);
		if(m[t]==undefined) m[t] = [];

		Array.prototype.unshift.call(m[t], v);
		return m;
	}, {});

	return types.map(picker=>{
		let v = Reflect.get(map, picker[0]);
		return v ? v.length>1 ? Array.prototype.pop.call(v) : v[0] : picker[1];
	}) as mtec.TypeArray<mtec.PickFirst<T>>;
}

mtec.delay = function(delay, call, ...args){
	return new Promise((s, j)=>{
		let id = setTimeout((c, a)=>{
			clearTimeout(id);
			s(typeof c=='function' ? c(...a) : c);
		}, delay * 1000, call, args)
	});
}

mtec.drawLots = function(bucket){
	if(bucket.length<=0) return void 0;
	if(bucket.length==1) return bucket[0][0];

	let data_list = [];
	let weight_list = [];
	let total = 0;

	for(let [data, weight] of bucket){
		let v = Number(weight);
		if(!(v > 0)) continue;

		total += v;
		data_list.push(data);
		weight_list.push(total);
	}

	let point = Math.random() * total;
	let i = 0;
	while(point >= weight_list[i]) i++;

	return data_list[i];
}

mtec.fusionData = function(target, ...list){
	if(target instanceof Object){
		if(Array.isArray(target)){
			//@ts-ignore
			list.forEach(data=>Array.isArray(data) ? target.push(...data) : target.push(data));
		}else if(!(target instanceof Function)){
			let ls = list.filter(data=>data instanceof Object && !(data instanceof Function));
			let map = ls.reduce((m, data, i)=>{
				Reflect.ownKeys(data).forEach(key=>m.set(key, i));
				return m;
			}, new Map());
			//@ts-ignore
			map.forEach((i, key)=>Reflect.set(target, key, Reflect.get(ls[i], key)));
		}
	}else{
		if(typeof target == 'bigint'){
			list.forEach(data=>{
				switch(typeof data){
					//@ts-ignore
					case 'boolean': data = Number(data); case 'object': case 'undefined': data = BigInt(0); case 'number': if(isNaN(data)) data = 0; data = BigInt(data); break;
					//@ts-ignore
					case 'symbol': data = String(data); break;
				}
				// @ts-ignore
				target += data;
			})
		}else if(['string', 'symbol'].includes(typeof target)){
			//@ts-ignore
			target = String(target);
			//@ts-ignore
			list.forEach(data=>target += (typeof data=='symbol' ? String(data) : data));
		}else{
			list.forEach(data=>{
				switch(typeof data){
					//@ts-ignore
					case 'bigint': target = Number(target); if(isNaN(target)) target = BigInt(0); break;
					//@ts-ignore
					case 'symbol': data = String(data);
				}
				//@ts-ignore
				target += data;
			})
		}
	}

	return target as any;
}

mtec.cloneData = function(data){
	let clone_one: any;

	function copy_origin(data: any){
		switch(typeof data){
			case 'function': return Function.prototype.bind.call(data, null);
			case 'symbol': return Symbol(data['description']);
			default: return data;
		}
	}

	if(['object', 'array'].includes(mtec.getype(data))){
		let clone_list: any[] = [];
		let clone_map: Map<any, any> = new Map();

		function add2clonelist(obj: any){
			if(!clone_list.includes(obj)){
				clone_list.push(obj);
			}

			if(!clone_map.has(obj)){
				clone_map.set(obj, Array.isArray(obj) ? [] : {});
			}

			return clone_map.get(obj);
		}

		clone_one = add2clonelist(data);

		let clone_index = 0;
		let clone_copy: any;
		let clone_data: any;
		let item_value: any;
		while(clone_index < clone_list.length){
			clone_data = clone_list[clone_index];
			clone_copy = clone_map.get(clone_data);

			if(Array.isArray(clone_data)){
				clone_data.forEach((el, idx)=>{
					if(['object', 'array'].includes(mtec.getype(el))){
						clone_copy[idx] = add2clonelist(el);
					}else{
						clone_copy[idx] = copy_origin(el);
					}
				});
			}else{
				Reflect.ownKeys(clone_data).forEach(key=>{
					item_value = Reflect.get(clone_data, key);
					if(['object', 'array'].includes(mtec.getype(item_value))){
						clone_copy[key] = add2clonelist(item_value);
					}else{
						clone_copy[key] = copy_origin(item_value);
					}
				});
			}

			clone_index = clone_index + 1;
		}

		clone_list = clone_map = undefined;
		clone_data = clone_copy = item_value = clone_index = undefined;
	}else{
		clone_one = copy_origin(data);
	}

	return clone_one;
}

mtec.CountAverage = class{
	private count = 0;
	private _average: number;

	constructor(init?: number){
		if(init) this.add(init);
	}

	public get average(){
		return this._average ?? 0;
	}

	public add(value: number){
		this.count++;
		this._average = this.average + (value - this.average) / this.count;
		return this._average;
	}

	public clean(){
		this.count = 0;
		this._average = 0;
	}
}

mtec.NudityPromise = class<V>{
	private inited: boolean;
	private status: 'waiting'|'resolve'|'reject';
	private result: any;
	private _resolve_call_: (value: V | PromiseLike<V>)=>void;
	private _reject_call_: (reason: any)=>void;

	public promise: Promise<V>;

	constructor(){
		this.inited = false;
		this.status = 'waiting';
		this.promise = new Promise((s, j)=>{
			this._resolve_call_ = s;
			this._reject_call_ = j;
			this.inited = true;
			this.reply();
		});
	}

	public resolve(value: V | PromiseLike<V>){
		this.re_call('resolve', value);
	}

	public reject(reason: any){
		this.re_call('reject', reason);
	}

	private re_call(status: 'resolve'|'reject', result: any){
		if(this.status=='waiting'){
			[this.status, this.result] = [status, result];
			this.reply();
		}
	}

	private reply(){
		if(this.inited && this.status!='waiting'){
			switch(this.status){
				case 'resolve': this._resolve_call_(this.result); break;
				case 'reject': this._reject_call_(this.result); break;
			}
		}
	}
}

mtec.JsonString = function (data) {
	if (typeof (data) === 'object') {
		if (data == null) {
			return 'null';
		}
	} else {
		return JSON.stringify(data);
	}

	let data_list: any[] = [];
	let copy_map: Map<any, any> = new Map();
	let token_map: Map<any, string> = new Map();
	let token_cache = [];

	function add2copylist(obj: any) {
		if (!data_list.includes(obj)) {
			data_list.push(obj);
		}

		if (!copy_map.has(obj)) {
			copy_map.set(obj, Array.isArray(obj) ? [] : {});
		}

		if (!token_map.has(obj)) {
			token_map.set(obj, mtec.string.randomToken(5, 36, tkn => !token_cache.includes(tkn)));
			token_cache.push(token_map.get(obj));
		}
	}

	add2copylist(data);

	let loop_index = 0;
	let data_item: any, copy_item: any, item_value: any;
	let key_list: Array<string | number | symbol>;
	let data_flag: [string, number | string][];
	while (loop_index < data_list.length) {
		data_item = data_list[loop_index];
		copy_item = copy_map.get(data_item);
		data_flag = Array.isArray(data_item) ? [] : undefined;
		key_list = data_flag ? mtec.array.create(data_item.length, i => i) : Reflect.ownKeys(data_item);

		key_list.forEach(key => {
			if (typeof (key) === 'symbol') {
			} else {
				item_value = Reflect.get(data_item, key);
				if (typeof (item_value) === 'object') {
					if (item_value == null) {
						Reflect.set(copy_item, key, null);
					} else if (token_map.has(item_value)) {
						Reflect.set(copy_item, key, '@:>>' + token_map.get(item_value));
					} else {
						add2copylist(item_value);
						if (data_flag) {
							Reflect.set(copy_item, key, copy_map.get(item_value));
							data_flag.push([token_map.get(item_value), key]);
						} else {
							Reflect.set(copy_item, key + '#:>>' + token_map.get(item_value), copy_map.get(item_value));
						}
					}
				} else {
					Reflect.set(copy_item, key, item_value);
				}
			}
		});

		if (data_flag?.length > 0) {
			copy_item.push(data_flag.map(el => el[1] + '#:>>' + el[0]).join(';'));
		} else { }

		loop_index = loop_index + 1;
	}

	return token_map.get(data) + '<<:#' + JSON.stringify(copy_map.get(data));
}

mtec.parseJson = function (json_string) {
	let data: any;
	if (json_string.includes('<<:#')) {
		let token: string;
		[token, json_string] = json_string.split('<<:#');
		data = JSON.parse(json_string);
		let data_map: Map<string, any> = new Map();
		data_map.set(token, data);

		let data_list = [data];
		let loop_index = 0;
		let data_item: any, item_value: any;
		let data_key: string;
		while (loop_index < data_list.length) {
			data_item = data_list[loop_index];

			if (Array.isArray(data_item)) {
				if (typeof (data_item.lastElement) === 'string' && data_item.lastElement.includes('#:>>')) {
					(<string>data_item.pop()).split(';')
						.map(item => item.split('#:>>'))
						.forEach(el => data_map.set(el[1], data_item[el[0]]));
				} else { }

				data_item.forEach((el, i, arr) => {
					if (typeof (el) === 'string' && el.includes('@:>>')) {
						arr[i] = data_map.get(el.replace('@:>>', ''));
					} else if (typeof (el) === 'object' && el !== null) {
						data_list.push(el);
					}
				});
			} else if (data_item != null) {
				Reflect.ownKeys(data_item)
				.forEach(key => {
					if (typeof (key) === 'symbol') {
					} else {
						item_value = Reflect.get(data_item, key);

						if (key.includes('#:>>')) {
							[data_key, token] = key.split('#:>>');
							data_map.set(token, item_value);
							data_list.push(item_value);
							Reflect.deleteProperty(data_item, key);
							Reflect.set(data_item, data_key, item_value);
						} else if (typeof (item_value) === 'string' && item_value.includes('@:>>')) {
							Reflect.set(data_item, key, data_map.get(item_value.replace('@:>>', '')));
						}
					}
				});
			}

			loop_index = loop_index + 1;
		}

		token = data_list = data_map = undefined;
		data_item = item_value = data_key = loop_index = undefined;
	} else {
		data = JSON.parse(json_string);
	}

	return data;
}
